using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
//
// DotNetNuke - http://www.dotnetnuke.com
// Copyright (c) 2002-2010
// by DotNetNuke Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//
using System.Linq;
using System.Threading;
using DotNetNuke.Collections.Internal;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Services.Log.EventLog;
using DotNetNuke.Entities.Host;

namespace DotNetNuke.Services.Scheduling.DNNScheduling
{

	static class Scheduler
	{

		public class CoreScheduler
		{

			#region "Private Shared Members"

			/////////////////////////////////
			//This is the heart of the scheduler mechanism.
			//This class manages running new events according
			//to the schedule.
			//
			//This class can also react to the three
			//scheduler events (Started, Progressing and Completed)
			/////////////////////////////////
			private static bool ThreadPoolInitialized = false;
			/////////////////////////////////
			//The MaxThreadCount establishes the maximum
			//threads you want running simultaneously
			//for spawning SchedulerClient processes
			/////////////////////////////////
			private static int MaxThreadCount;
			private static int ActiveThreadCount;
			/////////////////////////////////
			//If KeepRunning gets switched to false, 
			//the scheduler stops running.
			/////////////////////////////////
			private static bool ForceReloadSchedule = false;

			private static bool Debug = false;

			private static int NumberOfProcessGroups;
			private static SharedList<ScheduleItem> _ScheduleQueue;

			private static SharedList<ScheduleHistoryItem> _ScheduleInProgress;
			/////////////////////////////////
			//This is our array that holds the process group
			//where our threads will be kicked off.
			/////////////////////////////////

			private static ProcessGroup[] arrProcessGroup;
			/////////////////////////////////
			//A ReaderWriterLock will protect our objects
			//in memory from being corrupted by simultaneous
			//thread operations.  This block of code below
			//establishes variables to help keep track
			//of the ReaderWriter locks.
			/////////////////////////////////
			private static int ReaderTimeouts = 0;
			private static int WriterTimeouts = 0;
			private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(45);
			private static ReaderWriterLock objStatusReadWriteLock = new ReaderWriterLock();

			private static ScheduleStatus Status = ScheduleStatus.STOPPED;
			static CoreScheduler()
			{
				ReaderWriterLockStrategy lockStrategy = new ReaderWriterLockStrategy(LockRecursionPolicy.SupportsRecursion);

				_ScheduleQueue = new SharedList<ScheduleItem>(lockStrategy);
				_ScheduleInProgress = new SharedList<ScheduleHistoryItem>(lockStrategy);
			}
			#endregion

			#region "Public Shared Members"

			public static bool KeepThreadAlive = true;

			public static bool KeepRunning = true;
			/////////////////////////////////
			//FreeThreads tracks how many threads we have
			//free to work with at any given time.
			/////////////////////////////////
			public static int FreeThreads {
				get { return MaxThreadCount - ActiveThreadCount; }
			}

			#endregion

			#region "Private Shared Methods"

			/////////////////////////////////
			//This is a multi-thread safe method that adds
			//an item to the collection of schedule items in 
			//progress.
			/////////////////////////////////
			private static void AddToScheduleInProgress(ScheduleHistoryItem objScheduleHistoryItem)
			{
				if (!ScheduleInProgressContains(objScheduleHistoryItem)) {
					try {
						using (ISharedCollectionLock @lock = _ScheduleInProgress.GetWriteLock(LockTimeout)) {
							if (!ScheduleInProgressContains(objScheduleHistoryItem)) {
								_ScheduleInProgress.Add(objScheduleHistoryItem);
							}
						}
					} catch (ApplicationException ex) {
						// The writer lock request timed out.
						Interlocked.Increment(ref WriterTimeouts);
						DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
					}
				}
			}

			/////////////////////////////////
			//This is a multi-thread safe method that clears
			//the collection of schedule items in 
			//queue.
			/////////////////////////////////
			private static void ClearScheduleQueue()
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetWriteLock(LockTimeout)) {
						_ScheduleQueue.Clear();
					}
				} catch (ApplicationException ex) {
					// The writer lock request timed out.
					Interlocked.Increment(ref WriterTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
				}
			}

			private static int GetProcessGroup()
			{
				//return a random process group
				Random r = new Random();
				return r.Next(0, NumberOfProcessGroups - 1);
			}

			private static bool IsInProgress(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetReadLock(LockTimeout)) {
						return _ScheduleInProgress.Any(si => si.ScheduleID == objScheduleItem.ScheduleID);
					}
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return false;
				}
			}

			/////////////////////////////////
			//This is a multi-thread safe method that removes
			//an item from the collection of schedule items in 
			//progress.
			/////////////////////////////////
			private static void RemoveFromScheduleInProgress(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetWriteLock(LockTimeout)) {
						ScheduleHistoryItem item = _ScheduleInProgress.Where(si => si.ScheduleID == objScheduleItem.ScheduleID).SingleOrDefault();
						if ((item != null)) {
							_ScheduleInProgress.Remove(item);
						}
					}
				} catch (ApplicationException ex) {
					// The writer lock request timed out.
					Interlocked.Increment(ref WriterTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
				}
			}

			private static bool ScheduleInProgressContains(ScheduleHistoryItem objScheduleHistoryItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetReadLock(LockTimeout)) {
						return _ScheduleInProgress.Any(si => si.ScheduleID == objScheduleHistoryItem.ScheduleID);
					}
				} catch (ApplicationException ex) {
					Interlocked.Increment(ref ReaderTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
					return false;
				}
			}

			private static bool ScheduleQueueContains(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetReadLock(LockTimeout)) {
						return _ScheduleQueue.Any(si => si.ScheduleID == objScheduleItem.ScheduleID);
					}
				} catch (ApplicationException ex) {
					Interlocked.Increment(ref ReaderTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
					return false;
				}
			}

			#endregion

			#region "Friend Shared Methods"

			static internal bool IsInQueue(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetReadLock(LockTimeout)) {
						return _ScheduleQueue.Any(si => si.ScheduleID == objScheduleItem.ScheduleID);
					}
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return false;
				}
			}

			#endregion

			#region "Public Shared Methods"

			public static ScheduleHistoryItem AddScheduleHistory(ScheduleHistoryItem objScheduleHistoryItem)
			{
				try {
					int intScheduleHistoryID = 0;
					intScheduleHistoryID = SchedulingController.AddScheduleHistory(objScheduleHistoryItem);

					objScheduleHistoryItem.ScheduleHistoryID = intScheduleHistoryID;

				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
				return objScheduleHistoryItem;
			}

			/////////////////////////////////
			//This is a multi-thread safe method that adds
			//an item to the collection of schedule items in 
			//queue.
			/////////////////////////////////
			public static void AddToScheduleQueue(ScheduleHistoryItem objScheduleHistoryItem)
			{
				if (!ScheduleQueueContains(objScheduleHistoryItem)) {
					try {
						//objQueueReadWriteLock.AcquireWriterLock(WriteTimeout)
						using (ISharedCollectionLock @lock = _ScheduleQueue.GetWriteLock(LockTimeout)) {
							//Do a second check just in case
							if (!ScheduleQueueContains(objScheduleHistoryItem) && !IsInProgress(objScheduleHistoryItem)) {
								// It is safe for this thread to read or write
								// from the shared resource.
								_ScheduleQueue.Add(objScheduleHistoryItem);
							}
						}
					} catch (ApplicationException ex) {
						// The writer lock request timed out.
						Interlocked.Increment(ref WriterTimeouts);
						DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
					}
				}
			}

			public static void FireEvents()
			{
				///////////////////////////////////////////////////
				//This method uses a thread pool to
				//call the SchedulerClient methods that need
				//to be called.
				//////////////////////////////////////////////////

				//////////////////////////////////////////////////
				//For each item in the queue that there
				//is an open thread for, set the object
				//in the array to a new ProcessGroup object.
				//Pass in the ScheduleItem to the ProcessGroup
				//so the ProcessGroup can pass it around for
				//logging and notifications.
				//////////////////////////////////////////////////
				using (ISharedCollectionLock @lock = _ScheduleQueue.GetReadLock(LockTimeout)) {
					int numToRun = _ScheduleQueue.Count;
					int numRun = 0;

					foreach (ScheduleItem objScheduleItem in _ScheduleQueue) {
						if (KeepRunning == false)
							return;
						int ProcessGroup = GetProcessGroup();
						//Dim objScheduleItem As ScheduleItem = CType(ScheduleQueue(i + 1), ScheduleItem)

						if (objScheduleItem.NextStart <= DateTime.Now && objScheduleItem.Enabled && !IsInProgress(objScheduleItem) && !HasDependenciesConflict(objScheduleItem) && numRun < numToRun) {
							objScheduleItem.ProcessGroup = ProcessGroup;
							if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD) {
								objScheduleItem.ScheduleSource = ScheduleSource.STARTED_FROM_TIMER;
							} else if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.REQUEST_METHOD) {
								objScheduleItem.ScheduleSource = ScheduleSource.STARTED_FROM_BEGIN_REQUEST;
							}

							arrProcessGroup[ProcessGroup].AddQueueUserWorkItem(objScheduleItem);

							LogEventAddedToProcessGroup(objScheduleItem);
							numRun += 1;
						} else {
							LogWhyTaskNotRun(objScheduleItem);
						}
					}
				}
			}


			private static void LogWhyTaskNotRun(ScheduleItem objScheduleItem)
			{
				if (Debug) {
					bool appended = false;
					System.Text.StringBuilder strDebug = new System.Text.StringBuilder("Task not run because ");
					if (!(objScheduleItem.NextStart <= DateTime.Now)) {
						strDebug.Append(" task is scheduled for " + objScheduleItem.NextStart.ToString());
						appended = true;
					}
					//If Not (objScheduleItem.NextStart <> Null.NullDate And objScheduleItem.ScheduleSource <> ScheduleSource.STARTED_FROM_EVENT) Then
					//    If appended Then strDebug.Append(" and")
					//    strDebug.Append(" task's NextStart <> NullDate and it's wasn't started from an EVENT")
					//    appended = True
					//End If
					if (!objScheduleItem.Enabled) {
						if (appended)
							strDebug.Append(" and");
						strDebug.Append(" task is not enabled");
						appended = true;
					}
					if (IsInProgress(objScheduleItem)) {
						if (appended)
							strDebug.Append(" and");
						strDebug.Append(" task is already in progress");
						appended = true;
					}
					if (HasDependenciesConflict(objScheduleItem)) {
						if (appended)
							strDebug.Append(" and");
						strDebug.Append(" task has conflicting dependency");
						appended = true;
					}
					EventLogController objEventLog = new EventLogController();
					LogInfo objEventLogInfo = new LogInfo();
					objEventLogInfo.AddProperty("EVENT NOT RUN REASON", strDebug.ToString());
					objEventLogInfo.AddProperty("SCHEDULE ID", objScheduleItem.ScheduleID.ToString());
					objEventLogInfo.AddProperty("TYPE FULL NAME", objScheduleItem.TypeFullName);
					objEventLogInfo.LogTypeKey = "DEBUG";
					objEventLog.AddLog(objEventLogInfo);
				}
			}


			private static void LogEventAddedToProcessGroup(ScheduleItem objScheduleItem)
			{
				if (Debug == true) {
					EventLogController objEventLog = new EventLogController();
					LogInfo objEventLogInfo = new LogInfo();
					objEventLogInfo.AddProperty("EVENT ADDED TO PROCESS GROUP " + objScheduleItem.ProcessGroup.ToString(), objScheduleItem.TypeFullName);
					objEventLogInfo.AddProperty("SCHEDULE ID", objScheduleItem.ScheduleID.ToString());
					objEventLogInfo.LogTypeKey = "DEBUG";
					objEventLog.AddLog(objEventLogInfo);
				}
			}

			public static int GetActiveThreadCount()
			{
				return ActiveThreadCount;
			}

			public static int GetFreeThreadCount()
			{
				return FreeThreads;
			}

			public static int GetMaxThreadCount()
			{
				return MaxThreadCount;
			}

			/////////////////////////////////
			//This is a multi-thread safe method that returns
			//a copy of the collection of 
			//schedule items in progress.
			/////////////////////////////////
			public static SharedList<ScheduleItem> GetScheduleInProgress()
			{
				SharedList<ScheduleItem> c = new SharedList<ScheduleItem>();
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetReadLock(LockTimeout)) {
						foreach (ScheduleItem item in _ScheduleInProgress) {
							c.Add(item);
						}
					}
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
				}
				return c;
			}

			/////////////////////////////////
			//This is a multi-thread safe method that returns
			//the number of items in the collection of 
			//schedule items in progress.
			/////////////////////////////////
			public static int GetScheduleInProgressCount()
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetReadLock(LockTimeout)) {
						return _ScheduleInProgress.Count;
					}
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return 0;
				}
			}

			/////////////////////////////////
			//This is a multi-thread safe method that returns
			//a copy of collection of all schedule items in queue.
			/////////////////////////////////
			public static SharedList<ScheduleItem> GetScheduleQueue()
			{
				SharedList<ScheduleItem> c = new SharedList<ScheduleItem>();
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetReadLock(LockTimeout)) {
						foreach (ScheduleItem item in _ScheduleQueue) {
							c.Add(item);
						}
					}
					return c;
				} catch (ApplicationException ex) {
					Interlocked.Increment(ref ReaderTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
				}
				return c;
			}

			/////////////////////////////////
			//This is a multi-thread safe method that returns
			//the number of items in the collection of 
			//schedule items in queue.
			/////////////////////////////////
			public static int GetScheduleQueueCount()
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetReadLock(LockTimeout)) {
						return _ScheduleQueue.Count;
					}
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return 0;
				}
			}

			public static ScheduleStatus GetScheduleStatus()
			{
				try {
					objStatusReadWriteLock.AcquireReaderLock(LockTimeout);
					try {
						//ScheduleStatus is a value type a copy is returned (enumeration)
						return Status;
					} finally {
						//                         Ensure that the lock is released.
						objStatusReadWriteLock.ReleaseReaderLock();
					}
				} catch (ApplicationException ex) {
					//                     The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return ScheduleStatus.NOT_SET;
				}
			}

			public static void Halt(string SourceOfHalt)
			{
				if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD) {
					EventLogController objEventLog = new EventLogController();
					LogInfo objEventLogInfo = new LogInfo();
					SetScheduleStatus(ScheduleStatus.SHUTTING_DOWN);
					objEventLogInfo = new LogInfo();
					objEventLogInfo.AddProperty("Initiator", SourceOfHalt);
					objEventLogInfo.LogTypeKey = "SCHEDULER_SHUTTING_DOWN";
					objEventLog.AddLog(objEventLogInfo);

					KeepRunning = false;

					//wait for up to 120 seconds for thread
					//to shut down
					int i = 0;
					for (i = 0; i <= 120; i++) {
						if (GetScheduleStatus() == ScheduleStatus.STOPPED)
							return;
						Thread.Sleep(1000);
					}
				}
				ActiveThreadCount = 0;
			}

			public static bool HasDependenciesConflict(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleInProgress.GetReadLock(LockTimeout)) {
						if (objScheduleItem.ObjectDependencies.Any()) {
							foreach (ScheduleItem item in _ScheduleInProgress.Where(si => si.ObjectDependencies.Any())) {
								if (item.HasObjectDependencies(objScheduleItem.ObjectDependencies)) {
									return true;
								}
							}
						}
					}

					return false;
				} catch (ApplicationException ex) {
					// The reader lock request timed out.
					Interlocked.Increment(ref ReaderTimeouts);
					return false;
				}
			}

			public static void LoadQueueFromEvent(EventName EventName)
			{
				List<ScheduleItem> schedule = SchedulingController.GetScheduleByEvent(EventName.ToString(), ServerController.GetExecutingServerName());

				for (int i = 0; i <= schedule.Count - 1; i++) {
					ScheduleHistoryItem objScheduleHistoryItem = new ScheduleHistoryItem(schedule[i]);

					if (!IsInQueue(objScheduleHistoryItem) && !IsInProgress(objScheduleHistoryItem) && !HasDependenciesConflict(objScheduleHistoryItem) && objScheduleHistoryItem.Enabled) {
						objScheduleHistoryItem.ScheduleSource = ScheduleSource.STARTED_FROM_EVENT;
						AddToScheduleQueue(objScheduleHistoryItem);
					}
				}

			}

			public static void LoadQueueFromTimer()
			{
				ForceReloadSchedule = false;

				List<ScheduleItem> schedule = SchedulingController.GetSchedule(ServerController.GetExecutingServerName());

				for (int i = 0; i <= schedule.Count - 1; i++) {
					ScheduleHistoryItem objScheduleHistoryItem = new ScheduleHistoryItem(schedule[i]);

					if (!IsInQueue(objScheduleHistoryItem) && !(objScheduleHistoryItem.TimeLapse == Null.NullInteger) && !(objScheduleHistoryItem.TimeLapseMeasurement == Null.NullString) && objScheduleHistoryItem.Enabled) {
						if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD) {
							objScheduleHistoryItem.ScheduleSource = ScheduleSource.STARTED_FROM_TIMER;
						} else if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.REQUEST_METHOD) {
							objScheduleHistoryItem.ScheduleSource = ScheduleSource.STARTED_FROM_BEGIN_REQUEST;
						}
						AddToScheduleQueue(objScheduleHistoryItem);
					}
				}

			}

			public static void PurgeScheduleHistory()
			{
				SchedulingController.PurgeScheduleHistory();
			}

			public static void ReloadSchedule()
			{
				ForceReloadSchedule = true;
			}

			/////////////////////////////////
			//This is a multi-thread safe method that removes
			//an item from the collection of schedule items in 
			//queue.
			/////////////////////////////////
			public static void RemoveFromScheduleQueue(ScheduleItem objScheduleItem)
			{
				try {
					using (ISharedCollectionLock @lock = _ScheduleQueue.GetWriteLock(LockTimeout)) {
						//the scheduleitem instances may not be equal even though the scheduleids are equal
						ScheduleItem item = _ScheduleQueue.Where(si => si.ScheduleID == objScheduleItem.ScheduleID).SingleOrDefault();
						if ((item != null)) {
							_ScheduleQueue.Remove(item);
						}
					}
				} catch (ApplicationException ex) {
					// The writer lock request timed out.
					Interlocked.Increment(ref WriterTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
				}

			}

			public static void RunEventSchedule(EventName EventName)
			{
				try {
					EventLogController objEventLog = new EventLogController();
					LogInfo objEventLogInfo = new LogInfo();
					objEventLogInfo.AddProperty("EVENT", EventName.ToString());
					objEventLogInfo.LogTypeKey = "SCHEDULE_FIRED_FROM_EVENT";
					objEventLog.AddLog(objEventLogInfo);

					/////////////////////////////////
					//We allow for three threads to run simultaneously.
					//As long as we have an open thread, continue.
					/////////////////////////////////
					/////////////////////////////////
					//Load the queue to determine which schedule
					//items need to be run. 
					/////////////////////////////////
					LoadQueueFromEvent(EventName);

					while (GetScheduleQueueCount() > 0) {
						SetScheduleStatus(ScheduleStatus.RUNNING_EVENT_SCHEDULE);

						/////////////////////////////////
						//Fire off the events that need running.
						/////////////////////////////////
						if (GetScheduleQueueCount() > 0) {
							FireEvents();
						}


						if (WriterTimeouts > 20 || ReaderTimeouts > 20) {
							/////////////////////////////////
							//Wait for 10 minutes so we don't fill up the logs
							/////////////////////////////////
							Thread.Sleep(TimeSpan.FromMinutes(10));
						} else {
							/////////////////////////////////
							//Wait for 10 seconds to avoid cpu overutilization
							/////////////////////////////////
							Thread.Sleep(TimeSpan.FromSeconds(10));
						}

						if (GetScheduleQueueCount() == 0)
							return;

					}

				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);

				}

			}

			public static void SetScheduleStatus(ScheduleStatus newStatus)
			{
				try {
					//note:locking inside this method is highly misleading
					//as there is no lock in place between when the caller
					//decides to call this method and when the lock is acquired
					//the value could easily change in that time
					objStatusReadWriteLock.AcquireWriterLock(LockTimeout);
					try {
						// It is safe for this thread to read or write
						// from the shared resource.
						Status = newStatus;

					} finally {
						// Ensure that the lock is released.
						objStatusReadWriteLock.ReleaseWriterLock();
					}
				} catch (ApplicationException ex) {
					// The writer lock request timed out.
					Interlocked.Increment(ref WriterTimeouts);
					DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
				}

			}

			public static void Start()
			{
				try {
					ActiveThreadCount = 0;

					/////////////////////////////////
					//This is where the action begins.
					//Loop until KeepRunning = false
					/////////////////////////////////
					if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode != SchedulerMode.REQUEST_METHOD || Debug == true) {
						EventLogController objEventLog = new EventLogController();
						LogInfo objEventLogInfo = new LogInfo();
						objEventLogInfo.LogTypeKey = "SCHEDULER_STARTED";
						objEventLog.AddLog(objEventLogInfo);
					}
					while (KeepRunning == true) {
						try {
							if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD) {
								SetScheduleStatus(ScheduleStatus.RUNNING_TIMER_SCHEDULE);
							} else {
								SetScheduleStatus(ScheduleStatus.RUNNING_REQUEST_SCHEDULE);
							}
							/////////////////////////////////
							//Load the queue to determine which schedule
							//items need to be run. 
							/////////////////////////////////

							LoadQueueFromTimer();

							/////////////////////////////////
							//Keep track of when the queue was last refreshed
							//so we can perform a refresh periodically
							/////////////////////////////////
							System.DateTime LastQueueRefresh = DateTime.Now;

							bool RefreshQueueSchedule = false;

							/////////////////////////////////
							//We allow for [MaxThreadCount] threads to run 
							//simultaneously.  As long as we have an open thread
							//and we don't have to refresh the queue, continue
							//to loop.
							/////////////////////////////////
							while (FreeThreads > 0 && RefreshQueueSchedule == false && KeepRunning == true && ForceReloadSchedule == false) {
								/////////////////////////////////
								//Fire off the events that need running.
								/////////////////////////////////
								if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD) {
									SetScheduleStatus(ScheduleStatus.RUNNING_TIMER_SCHEDULE);
								} else {
									SetScheduleStatus(ScheduleStatus.RUNNING_REQUEST_SCHEDULE);
								}

								// It is safe for this thread to read from
								// the shared resource.
								if (GetScheduleQueueCount() > 0) {
									FireEvents();
								}
								if (KeepThreadAlive == false) {
									return;
								}


								if (WriterTimeouts > 20 || ReaderTimeouts > 20) {
									/////////////////////////////////
									//Some kind of deadlock on a resource.
									//Wait for 10 minutes so we don't fill up the logs
									/////////////////////////////////
									if (KeepRunning == true) {
										Thread.Sleep(TimeSpan.FromMinutes(10));
									} else {
										return;
									}
								} else {
									/////////////////////////////////
									//Wait for 10 seconds to avoid cpu overutilization
									/////////////////////////////////
									if (KeepRunning == true) {
										Thread.Sleep(TimeSpan.FromSeconds(10));
									} else {
										return;
									}

									//Refresh queue from database every 10 minutes
									//if there are no items currently in progress
									if ((LastQueueRefresh.AddMinutes(10) <= DateTime.Now || ForceReloadSchedule) && FreeThreads == MaxThreadCount) {
										RefreshQueueSchedule = true;
										break; // TODO: might not be correct. Was : Exit While
									}
								}

							}
							/////////////////////////////////
							//There are no available threads, all threads are being
							//used.  Wait 10 seconds until one is available
							/////////////////////////////////
							if (KeepRunning == true) {
								if (RefreshQueueSchedule == false) {
									SetScheduleStatus(ScheduleStatus.WAITING_FOR_OPEN_THREAD);
									Thread.Sleep(10000);
									//sleep for 10 seconds
								}
							} else {
								return;
							}
						} catch (Exception exc) {
							DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
							/////////////////////////////////
							//sleep for 10 minutes
							/////////////////////////////////
							Thread.Sleep(600000);
						}
					}
				} finally {
					if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.TIMER_METHOD || DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode == SchedulerMode.DISABLED) {
						SetScheduleStatus(ScheduleStatus.STOPPED);
					} else {
						SetScheduleStatus(ScheduleStatus.WAITING_FOR_REQUEST);
					}
					if (DotNetNuke.Services.Scheduling.SchedulingProvider.SchedulerMode != SchedulerMode.REQUEST_METHOD || Debug == true) {
						EventLogController objEventLog = new EventLogController();
						LogInfo objEventLogInfo = new LogInfo();
						objEventLogInfo.LogTypeKey = "SCHEDULER_STOPPED";
						objEventLog.AddLog(objEventLogInfo);
					}
				}
			}

			public static void UpdateScheduleHistory(ScheduleHistoryItem objScheduleHistoryItem)
			{
				try {
					SchedulingController.UpdateScheduleHistory(objScheduleHistoryItem);
				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
			}

			public static void WorkCompleted(SchedulerClient objSchedulerClient)
			{

				try {
					ScheduleHistoryItem objScheduleHistoryItem = default(ScheduleHistoryItem);
					objScheduleHistoryItem = objSchedulerClient.ScheduleHistoryItem;

					/////////////////////////////////
					//Remove the object in the ScheduleInProgress collection
					/////////////////////////////////
					RemoveFromScheduleInProgress(objScheduleHistoryItem);

					/////////////////////////////////
					//A SchedulerClient is notifying us that their
					//process has completed.  Decrease our ActiveThreadCount
					/////////////////////////////////
					Interlocked.Decrement(ref ActiveThreadCount);

					/////////////////////////////////
					//Update the schedule item object property
					//to note the end time and next start
					/////////////////////////////////
					objScheduleHistoryItem.EndDate = DateTime.Now;

					if (objScheduleHistoryItem.ScheduleSource == ScheduleSource.STARTED_FROM_EVENT) {
						objScheduleHistoryItem.NextStart = Null.NullDate;
					} else {
						if (objScheduleHistoryItem.CatchUpEnabled == true) {
							switch (objScheduleHistoryItem.TimeLapseMeasurement) {
								case "s":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.NextStart.AddSeconds(objScheduleHistoryItem.TimeLapse);
									break;
								case "m":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.NextStart.AddMinutes(objScheduleHistoryItem.TimeLapse);
									break;
								case "h":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.NextStart.AddHours(objScheduleHistoryItem.TimeLapse);
									break;
								case "d":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.NextStart.AddDays(objScheduleHistoryItem.TimeLapse);
									break;
							}
						} else {
							switch (objScheduleHistoryItem.TimeLapseMeasurement) {
								case "s":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddSeconds(objScheduleHistoryItem.TimeLapse);
									break;
								case "m":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddMinutes(objScheduleHistoryItem.TimeLapse);
									break;
								case "h":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddHours(objScheduleHistoryItem.TimeLapse);
									break;
								case "d":
									objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddDays(objScheduleHistoryItem.TimeLapse);
									break;
							}
						}
					}

					/////////////////////////////////
					//Update the ScheduleHistory in the database
					/////////////////////////////////
					CoreScheduler.UpdateScheduleHistory(objScheduleHistoryItem);
					LogInfo objEventLogInfo = new LogInfo();

					if (objScheduleHistoryItem.NextStart != Null.NullDate) {
						/////////////////////////////////
						//Put the object back into the ScheduleQueue
						//collection with the new NextStart date.
						/////////////////////////////////
						objScheduleHistoryItem.StartDate = Null.NullDate;
						objScheduleHistoryItem.EndDate = Null.NullDate;
						objScheduleHistoryItem.LogNotes = "";
						objScheduleHistoryItem.ProcessGroup = -1;
						AddToScheduleQueue(objScheduleHistoryItem);
					}


					if (objSchedulerClient.ScheduleHistoryItem.RetainHistoryNum > 0) {
						/////////////////////////////////
						//Write out the log entry for this event
						/////////////////////////////////
						EventLogController objEventLog = new EventLogController();

						objEventLogInfo.AddProperty("TYPE", objSchedulerClient.GetType().FullName);
						objEventLogInfo.AddProperty("THREAD ID", Thread.CurrentThread.GetHashCode().ToString());
						objEventLogInfo.AddProperty("NEXT START", Convert.ToString(objScheduleHistoryItem.NextStart));
						objEventLogInfo.AddProperty("SOURCE", objSchedulerClient.ScheduleHistoryItem.ScheduleSource.ToString());
						objEventLogInfo.AddProperty("ACTIVE THREADS", ActiveThreadCount.ToString());
						objEventLogInfo.AddProperty("FREE THREADS", FreeThreads.ToString());
						objEventLogInfo.AddProperty("READER TIMEOUTS", ReaderTimeouts.ToString());
						objEventLogInfo.AddProperty("WRITER TIMEOUTS", WriterTimeouts.ToString());
						objEventLogInfo.AddProperty("IN PROGRESS", GetScheduleInProgressCount().ToString());
						objEventLogInfo.AddProperty("IN QUEUE", GetScheduleQueueCount().ToString());
						objEventLogInfo.LogTypeKey = "SCHEDULER_EVENT_COMPLETED";
						objEventLog.AddLog(objEventLogInfo);
					}
				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
			}

			public static void WorkErrored(SchedulerClient objSchedulerClient, Exception objException)
			{

				try {
					ScheduleHistoryItem objScheduleHistoryItem = default(ScheduleHistoryItem);
					objScheduleHistoryItem = objSchedulerClient.ScheduleHistoryItem;
					///'''''''''''''''''''''''''''''''''''''''''''''''
					//Remove the object in the ScheduleInProgress collection
					///'''''''''''''''''''''''''''''''''''''''''''''''
					RemoveFromScheduleInProgress(objScheduleHistoryItem);

					/////////////////////////////////
					//A SchedulerClient is notifying us that their
					//process has errored.  Decrease our ActiveThreadCount
					/////////////////////////////////
					Interlocked.Decrement(ref ActiveThreadCount);


					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(objException);

					/////////////////////////////////////////////
					//Update the schedule item object property
					//to note the end time and next start
					////////////////////////////////////////////
					objScheduleHistoryItem.EndDate = DateTime.Now;
					if (objScheduleHistoryItem.ScheduleSource == ScheduleSource.STARTED_FROM_EVENT) {
						objScheduleHistoryItem.NextStart = Null.NullDate;

					} else if (objScheduleHistoryItem.RetryTimeLapse != Null.NullInteger) {
						switch (objScheduleHistoryItem.RetryTimeLapseMeasurement) {
							case "s":
								objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddSeconds(objScheduleHistoryItem.RetryTimeLapse);
								break;
							case "m":
								objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddMinutes(objScheduleHistoryItem.RetryTimeLapse);
								break;
							case "h":
								objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddHours(objScheduleHistoryItem.RetryTimeLapse);
								break;
							case "d":
								objScheduleHistoryItem.NextStart = objScheduleHistoryItem.StartDate.AddDays(objScheduleHistoryItem.RetryTimeLapse);
								break;
						}

					}
					/////////////////////////////////
					//Update the ScheduleHistory in the database
					/////////////////////////////////
					CoreScheduler.UpdateScheduleHistory(objScheduleHistoryItem);

					if (objScheduleHistoryItem.NextStart != Null.NullDate && objScheduleHistoryItem.RetryTimeLapse != Null.NullInteger) {
						/////////////////////////////////
						//Put the object back into the ScheduleQueue
						//collection with the new NextStart date.
						/////////////////////////////////
						objScheduleHistoryItem.StartDate = Null.NullDate;
						objScheduleHistoryItem.EndDate = Null.NullDate;
						objScheduleHistoryItem.LogNotes = "";
						objScheduleHistoryItem.ProcessGroup = -1;
						AddToScheduleQueue(objScheduleHistoryItem);
					}

					if (objSchedulerClient.ScheduleHistoryItem.RetainHistoryNum > 0) {
						/////////////////////////////////
						//Write out the log entry for this event
						/////////////////////////////////
						EventLogController objEventLog = new EventLogController();
						LogInfo objEventLogInfo = new LogInfo();
						objEventLogInfo.AddProperty("THREAD ID", Thread.CurrentThread.GetHashCode().ToString());
						objEventLogInfo.AddProperty("TYPE", objSchedulerClient.GetType().FullName);
						if ((objException != null)) {
							objEventLogInfo.AddProperty("EXCEPTION", objException.Message);
						}
						objEventLogInfo.AddProperty("RESCHEDULED FOR", Convert.ToString(objScheduleHistoryItem.NextStart));
						objEventLogInfo.AddProperty("SOURCE", objSchedulerClient.ScheduleHistoryItem.ScheduleSource.ToString());
						objEventLogInfo.AddProperty("ACTIVE THREADS", ActiveThreadCount.ToString());
						objEventLogInfo.AddProperty("FREE THREADS", FreeThreads.ToString());
						objEventLogInfo.AddProperty("READER TIMEOUTS", ReaderTimeouts.ToString());
						objEventLogInfo.AddProperty("WRITER TIMEOUTS", WriterTimeouts.ToString());
						objEventLogInfo.AddProperty("IN PROGRESS", GetScheduleInProgressCount().ToString());
						objEventLogInfo.AddProperty("IN QUEUE", GetScheduleQueueCount().ToString());
						objEventLogInfo.LogTypeKey = "SCHEDULER_EVENT_FAILURE";
						objEventLog.AddLog(objEventLogInfo);
					}
				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
			}

			public static void WorkProgressing(SchedulerClient objSchedulerClient)
			{
				try {
					/////////////////////////////////
					//A SchedulerClient is notifying us that their
					//process is in progress.  Informational only.
					/////////////////////////////////
					if (objSchedulerClient.ScheduleHistoryItem.RetainHistoryNum > 0) {
						/////////////////////////////////
						//Write out the log entry for this event
						/////////////////////////////////
						EventLogController objEventLog = new EventLogController();
						LogInfo objEventLogInfo = new LogInfo();
						objEventLogInfo.AddProperty("THREAD ID", Thread.CurrentThread.GetHashCode().ToString());
						objEventLogInfo.AddProperty("TYPE", objSchedulerClient.GetType().FullName);
						objEventLogInfo.AddProperty("SOURCE", objSchedulerClient.ScheduleHistoryItem.ScheduleSource.ToString());
						objEventLogInfo.AddProperty("ACTIVE THREADS", ActiveThreadCount.ToString());
						objEventLogInfo.AddProperty("FREE THREADS", FreeThreads.ToString());
						objEventLogInfo.AddProperty("READER TIMEOUTS", ReaderTimeouts.ToString());
						objEventLogInfo.AddProperty("WRITER TIMEOUTS", WriterTimeouts.ToString());
						objEventLogInfo.AddProperty("IN PROGRESS", GetScheduleInProgressCount().ToString());
						objEventLogInfo.AddProperty("IN QUEUE", GetScheduleQueueCount().ToString());
						objEventLogInfo.LogTypeKey = "SCHEDULER_EVENT_PROGRESSING";
						objEventLog.AddLog(objEventLogInfo);
					}
				} catch (Exception exc) {
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
			}

			public static void WorkStarted(SchedulerClient objSchedulerClient)
			{
				bool ActiveThreadCountIncremented = false;

				try {
					objSchedulerClient.ScheduleHistoryItem.ThreadID = Thread.CurrentThread.GetHashCode();

					/////////////////////////////////
					//Put the object in the ScheduleInProgress collection
					//and remove it from the ScheduleQueue
					/////////////////////////////////
					RemoveFromScheduleQueue(objSchedulerClient.ScheduleHistoryItem);
					AddToScheduleInProgress(objSchedulerClient.ScheduleHistoryItem);

					/////////////////////////////////
					//A SchedulerClient is notifying us that their
					//process has started.  Increase our ActiveThreadCount
					/////////////////////////////////
					Interlocked.Increment(ref ActiveThreadCount);
					ActiveThreadCountIncremented = true;

					/////////////////////////////////
					//Update the schedule item
					//object property to note the start time.
					/////////////////////////////////
					objSchedulerClient.ScheduleHistoryItem.StartDate = DateTime.Now;
					CoreScheduler.AddScheduleHistory(objSchedulerClient.ScheduleHistoryItem);


					if (objSchedulerClient.ScheduleHistoryItem.RetainHistoryNum > 0) {
						/////////////////////////////////
						//Write out the log entry for this event
						/////////////////////////////////
						EventLogController objEventLog = new EventLogController();
						LogInfo objEventLogInfo = new LogInfo();
						objEventLogInfo.AddProperty("THREAD ID", Thread.CurrentThread.GetHashCode().ToString());
						objEventLogInfo.AddProperty("TYPE", objSchedulerClient.GetType().FullName);
						objEventLogInfo.AddProperty("SOURCE", objSchedulerClient.ScheduleHistoryItem.ScheduleSource.ToString());
						objEventLogInfo.AddProperty("ACTIVE THREADS", ActiveThreadCount.ToString());
						objEventLogInfo.AddProperty("FREE THREADS", FreeThreads.ToString());
						objEventLogInfo.AddProperty("READER TIMEOUTS", ReaderTimeouts.ToString());
						objEventLogInfo.AddProperty("WRITER TIMEOUTS", WriterTimeouts.ToString());
						objEventLogInfo.AddProperty("IN PROGRESS", GetScheduleInProgressCount().ToString());
						objEventLogInfo.AddProperty("IN QUEUE", GetScheduleQueueCount().ToString());
						objEventLogInfo.LogTypeKey = "SCHEDULER_EVENT_STARTED";
						objEventLog.AddLog(objEventLogInfo);
					}
				} catch (Exception exc) {
					//Decrement the ActiveThreadCount because
					//otherwise the number of active threads
					//will appear to be climbing when in fact
					//no tasks are being executed.
					if (ActiveThreadCountIncremented) {
						Interlocked.Decrement(ref ActiveThreadCount);
					}
					DotNetNuke.Services.Exceptions.Exceptions.ProcessSchedulerException(exc);
				}
			}

			#endregion

			public CoreScheduler(int MaxThreads) : base()
			{
				if (!ThreadPoolInitialized) {
					InitializeThreadPool(MaxThreads);
				}
			}

			public CoreScheduler(bool boolDebug, int MaxThreads) : base()
			{
				Debug = boolDebug;
				if (!ThreadPoolInitialized) {
					InitializeThreadPool(MaxThreads);
				}
			}

			private void InitializeThreadPool(int MaxThreads)
			{
				if (MaxThreads == -1)
					MaxThreads = 1;
				NumberOfProcessGroups = MaxThreads;
				MaxThreadCount = MaxThreads;
				int i = 0;
				for (i = 0; i <= NumberOfProcessGroups - 1; i++) {
					Array.Resize(ref arrProcessGroup, i + 1);
					arrProcessGroup[i] = new ProcessGroup();
				}
				ThreadPoolInitialized = true;
			}

		}
	}
}